package com.zhaofujun.filequeue;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.InvocationTargetException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class SegmentFileMapper extends BaseFileMapper {

    protected int elementSize;

    private final RandomAccessFile lockRandomAccessFile;
    private final FileChannel lockFileChannel;
    private FileLock lock;

    private Path getLockFilePath() {
        return Paths.get(baseDir, ".lock");
    }


    public SegmentFileMapper(String baseDir, int segmentSize) throws IOException {
        //数据文件格式[头信息1：[索引:8][写入位置：8][写入长度:4]]*segmentSize + [数据:[数据1][数据2]...[数据n]]

        super(baseDir, segmentSize);

        this.lockRandomAccessFile = new RandomAccessFile(getLockFilePath().toFile(), "rw");
        this.lockFileChannel = this.lockRandomAccessFile.getChannel();
        this.lock = lockFileChannel.lock();

    }


    private final Map<Long, MappedByteBuffer> bufferMap = new HashMap<>();



    private MappedByteBuffer getSegmentBuffer(SegmentInfo segmentInfo, FileMode mode) {
        MappedByteBuffer mappedByteBuffer = bufferMap.get(segmentInfo.getSegmentNumber());
        if (mappedByteBuffer != null)
            return mappedByteBuffer;

        if (!segmentInfo.getSegmentFile().exists()) {
            //检查文件不存在时，根据模式进行处理
            if (mode == FileMode.WRITE) {
                long size = calculateFirstOffset() + elementSize * segmentSize * 2L;
                try (RandomAccessFile randomAccessFile = new RandomAccessFile(segmentInfo.getSegmentFile(), "rw");
                     FileChannel channel = randomAccessFile.getChannel()) {
                    mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, size);
                    bufferMap.put(segmentInfo.getSegmentNumber(), mappedByteBuffer);
                } catch (IOException e) {
                    throw new RuntimeException("create segment file failed in mode " + mode, e);
                }
            } else {
                throw new RuntimeException("File not exist in mode " + mode + " " + segmentInfo.getSegmentFile());
            }
        } else {
            //如果文件存在，则映射文件到内存
            try (RandomAccessFile randomAccessFile = new RandomAccessFile(segmentInfo.getSegmentFile(), "rw");
                 FileChannel channel = randomAccessFile.getChannel()) {
                mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());
                bufferMap.put(segmentInfo.getSegmentNumber(), mappedByteBuffer);
            } catch (IOException e) {
                throw new RuntimeException("map segment file failed in mode " + mode, e);
            }
        }
        return mappedByteBuffer;
    }

    //扩容
    private void expandSegmentBuffer(SegmentInfo segmentInfo) {
        //根据当前文件的大小与写入位置的比例进行扩容
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(segmentInfo.getSegmentFile(), "rw");
             FileChannel channel = randomAccessFile.getChannel()) {
            long fileSize = (long) (segmentSize / segmentInfo.getIndex()) * elementSize * 2 + channel.size();
            MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
            bufferMap.put(segmentInfo.getSegmentNumber(), mappedByteBuffer);
        } catch (IOException e) {
            throw new RuntimeException("expand segment file failed", e);
        }


    }

    public long put(DataInfo info) {

        SegmentInfo segmentInfo = info.getSegmentInfo();

        // 从缓冲区中获取片段
        this.elementSize = info.getHeadInfo().getBodySize();

        MappedByteBuffer mappedByteBuffer = getSegmentBuffer(segmentInfo, FileMode.WRITE);

        //检查当前缓冲区是否足够写入数据
        if (info.getHeadInfo().getBodyOffset() + info.getHeadInfo().getBodySize() > mappedByteBuffer.capacity()) {
            // 缓冲区不足，需要扩容文件
            expandSegmentBuffer(segmentInfo);
            mappedByteBuffer = getSegmentBuffer(segmentInfo, FileMode.WRITE);
        }

        // 写入头数据
        int headerOffset = segmentInfo.getIndex() * HeadInfo.SIZE;
        mappedByteBuffer.put(headerOffset, info.getHeadInfo().toBytes());
        // 写入数据
        mappedByteBuffer.put((int) (info.getHeadInfo().getBodyOffset()), info.getData());


        return info.getHeadInfo().getPosition();
    }

    public byte[] get(SegmentInfo segmentInfo) {
        MappedByteBuffer mappedByteBuffer = getSegmentBuffer(segmentInfo, FileMode.READ);

        int headerOffset = segmentInfo.getIndex() * HeadInfo.SIZE;

        //先读头数据
        byte[] headBytes = new byte[HeadInfo.SIZE];
        mappedByteBuffer.get(headerOffset, headBytes);
        HeadInfo headInfo = HeadInfo.fromBytes(headBytes);

        //读数据
        byte[] dataBytes = new byte[headInfo.getBodySize()];
        mappedByteBuffer.get((int) headInfo.getBodyOffset(), dataBytes);
        return dataBytes;
    }


    public void release() throws InvocationTargetException, IllegalAccessException {


        try {
            this.lock.release();
            this.lockFileChannel.close();
            this.lockRandomAccessFile.close();
        } catch (IOException e) {
            //已经关闭
            System.out.println("lock file already closed");
        }


        for (MappedByteBuffer mappedByteBuffer : bufferMap.values()) {
            release(mappedByteBuffer);
        }
        bufferMap.clear();

        //释放策略

    }

    public void deleteSegmentFile(long segmentNumber) throws IOException {

        //释放segmentNumber对应的缓冲区
        MappedByteBuffer mappedByteBuffer = bufferMap.get(segmentNumber);
        if (mappedByteBuffer != null) {
            release(mappedByteBuffer);
            bufferMap.remove(segmentNumber);
        }

        Path segmentFile = getSegmentFile(segmentNumber);
        if (Files.exists(segmentFile)) {
            Files.delete(segmentFile);
        }
    }


}

